# encoding: utf-8
"""
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    created by lane.chang on '17/10/2024'
    comment: 节点项目
"""
import ujson
from fastapi import UploadFile, File
from fastapi import APIRouter, Request, Body, Query, Form
from typing import Optional

from config import Config
from project.core.exception import NoRecordError, BizError
from project.core.function import json_response
from project.lib.decorator import api_common, exec_duration
from project.lib.constant import DEFAULT_VALUE
from project.lib.common import Global
from project.model.database.agent import Agent, AgentKnowledgeRelation
from project.model.database.knowledge import Knowledge, KnowledgeData, KnowledgeDetail
from project.service.knowledge import KnowledgeService
from project.model.basic import KnowledgeDataStatus, FlushFrequency


knowledge_router = APIRouter(prefix='/m', tags=['manager.knowledge'])


@knowledge_router.post('/knowledge/add', summary='新增知识库')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True)
async def do_knowledge_add(request: Request,
                           title: str = Body(..., max_length=30, title='标题'),
                           sub_title: str = Body('', max_length=30, title='副标题'),
                           image: str = Body('', max_length=200, title='图片'),
                           session=None,
                           body_params=None,
                           operator=None):
    """
    **desc**: 新增知识库

    **url**: http(s)://{0}.udicaria.com/m/knowledge/add

    **method**: POST

    **params**:

        <body>:
            title: 标题
            sub_title: 副标题
            image: 图片

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    knowledge = await KnowledgeService.do_knowledge_add(session, operator, body_params)

    return json_response(data=knowledge.dict('id', 'code'))


@knowledge_router.post('/knowledge/update', summary='修改知识库')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True)
async def do_knowledge_update(request: Request,
                              id: int = Body(..., title='知识库id'),
                              title: str = Body(DEFAULT_VALUE, max_length=30, title='标题'),
                              sub_title: str = Body(DEFAULT_VALUE, max_length=30, title='副标题'),
                              image: str = Body(DEFAULT_VALUE, max_length=200, title='图片'),
                              session=None,
                              body_params=None):
    """
    **desc**: 修改知识库

    **url**: http(s)://{0}.udicaria.com/m/knowledge/update

    **method**: POST

    **params**:

        <body>:
            id: 知识库id
            title: 标题
            sub_title: 副标题
            image: 图片

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    try:
        await Knowledge.create_modify(session, wheres=(Knowledge.id == id), no_record_error=True, **body_params.dict())
    except NoRecordError:
        raise BizError('知识库不存在')

    return json_response()


@knowledge_router.get('/knowledge/list', summary='知识库列表')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True)
async def get_knowledge_list(request: Request, title: str = Query('', title='知识库标题'), session=None, operator=None):
    """
    **desc**: 知识库列表

    **url**: http(s)://{0}.udicaria.com/m/knowledge/list

    **method**: GET

    **params**:

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data: [
                {
                    title: 知识库标题
                    sub_title: 知识库副标题
                    image: 知识库图片
                    bind_agent_count: 关联agent数量
                }
            ]
            exec_time: 接口后端处理时长
        }
    """
    wheres = list()
    if title:
        wheres.append(Knowledge.title.like(f'%{title}%'))
    wheres.append(Knowledge.organization_code == operator.organization_code)
    wheres.append(~Knowledge.is_deleted)

    data = await Knowledge.fetch_all(session, wheres=wheres, orders=(Knowledge.id.desc()))
    items = [v.dict() for v in data]

    return json_response(data=items)


@knowledge_router.post('/knowledge/delete', summary='删除知识库')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True)
async def do_knowledge_delete(request: Request,
                              id: int = Body(..., title='知识库id', embed=True),
                              session=None,
                              body_params=None):
    """
    **desc**: 删除知识库

    **url**: http(s)://{0}.udicaria.com/m/knowledge/delete

    **method**: POST

    **params**:

        <body>:
            id: 知识库id

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    try:
        rec = await AgentKnowledgeRelation.fetch_one(session, wheres=(AgentKnowledgeRelation.knowledge_id == id, ~Agent.is_deleted))
        if rec:
            raise BizError('知识库已被关联, 不允许删除')

        await Knowledge.create_modify(session, wheres=(Knowledge.id == id), no_record_error=True, is_deleted=1)
    except NoRecordError:
        raise BizError('知识库不存在')

    return json_response()


@knowledge_router.post('/knowledge/test', summary='知识库搜索测试')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True)
async def do_knowledge_test(request: Request,
                            id: int = Body(..., title='知识库id'),
                            text: str = Body(..., title='测试文本'),
                            session=None):
    """
    **desc**: 知识库搜索测试

    **url**: http(s)://{0}.udicaria.com/m/knowledge/test

    **method**: POST

    **params**:

        <body>:
            id: 知识库id
            text: 测试文本

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data: {
                id: 知识库数据集id

            }
            exec_time: 接口后端处理时长
        }
    """
    data = await KnowledgeService.do_knowledge_test(session, id, text)

    return json_response(data=data)


@knowledge_router.post('/knowledge/data/file/import', summary='知识库数据(文件导入)')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True,)
async def do_knowledge_data_file_import(request: Request,
                                        id: int = Form(..., title='知识库id'),
                                        files: list[UploadFile] = File(..., title='上传文件'),
                                        session=None):
    """
    **desc**: 添加数据库数据(文件导入)

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/file/import

    **method**: POST

    **params**:

        <body>:
            id: 知识库id
            files: 要导入的文件列表

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    await KnowledgeService.do_knowledge_data_file_import(session, id, files)

    return json_response()


@knowledge_router.post('/knowledge/data/feishu/import', summary='知识库数据(飞书导入)')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True,)
async def do_knowledge_data_feishu_import(request: Request,
                                          id: int = Body(..., title='知识库id'),
                                          feishu_urls: list[str] = Body(..., title='飞书云文档链接列表'),
                                          flush_frequency: FlushFrequency.enums(fixed=Global.count()) = Body(..., title='更新频率', description='minute(按分钟)/hour(按小时)/day(按天)/no_auto(不自动更新)'),
                                          flush_frequency_value: int = Body(0, title='更新频率值'),
                                          session=None):
    """
    **desc**: 添加数据库数据(飞书导入)

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/feishu/import

    **method**: POST

    **params**:

        <body>:
            id: 知识库id
            feishu_urls: 飞书链接列表
            flush_frequency: 更新频率 minute(按分钟)/hour(按小时)/day(按天)/no_auto(不自动更新)
            flush_frequency_value: 更新频率值

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    knowledge_datas = await KnowledgeService.do_knowledge_data_feishu_import(session, id, feishu_urls, flush_frequency, flush_frequency_value)

    if knowledge_datas:
        knowledge_data_ids = [v.id for v in knowledge_datas]
        await KnowledgeService.set_feishu_flush_crontab_batch(session, knowledge_data_ids)

    return json_response()


@knowledge_router.post('/knowledge/data/file/import/callback', summary='知识库数据(文件导入)(回调)')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),))
@exec_duration(add_return=True,)
async def knowledge_data_file_import_callback(request: Request, session=None):
    """
    **desc**: 添加数据库数据(回调)

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/file/import/callback

    **method**: POST

    **params**:

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    await KnowledgeService.knowledge_data_file_import_callback(session, ujson.loads(request.state.body_params))

    return json_response()


@knowledge_router.post('/knowledge/data/update', summary='知识库数据修改')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True,)
async def do_knowledge_data_update(request: Request,
                                   id: int = Body(..., title='知识库数据id'),
                                   title: str = Body(DEFAULT_VALUE, max_length=30, title='知识库数据标题'),
                                   flush_frequency: FlushFrequency.enums(fixed=Global.count()) = Body(DEFAULT_VALUE, title='更新频率', description='minute(按分钟)/hour(按小时)/day(按天)/no_auto(不自动更新)'),
                                   flush_frequency_value: int = Body(DEFAULT_VALUE, title='更新频率值'),
                                   session=None,
                                   body_params=None):
    """
    **desc**: 知识库数据修改

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/update

    **method**: POST

    **params**:

        <query>:
            id: 知识库数据id
            title: 知识库数据标题
            flush_frequency: 更新频率
            flush_frequency_value: 更新频率值

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    try:
        knowledge_data = await KnowledgeData.create_modify(session, wheres=(KnowledgeData.id == id), no_record_error=True, **body_params.dict())
        if flush_frequency or flush_frequency_value:
            await KnowledgeService.set_feishu_flush_crontab(session, knowledge_data.id)
    except NoRecordError:
        raise BizError('知识库数据不存在')

    return json_response()


@knowledge_router.get('/knowledge/data/list', summary='知识库数据列表')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True,)
async def get_knowledge_data_list(request: Request,
                                  id: int = Query(..., title='知识库id'),
                                  title: str = Query('', title='知识库数据集标题'),
                                  session=None):
    """
    **desc**: 知识库数据列表

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/list

    **method**: GET

    **params**:

        <query>:
            id: 知识库id
            title: 知识库数据集标题

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data: [
                {
                    title: 知识库数据名称
                    file_name: 导入文件名称
                    status: 知识库数据状态 状态 init(初始化)/importing(文本导入中)/splitting(切分中)/embedding(向量化中)/success(已完成)
                    status_desc: 状态描述
                    create_time: 时间
                    import_type: 导入类型 local_file(本地文件)/feishu_url(飞书链接)
                }
            ]
            exec_time: 接口后端处理时长
        }
    """
    knowledge_datas = await KnowledgeData.fetch_all(session, wheres=(KnowledgeData.knowledge_id == id, KnowledgeData.title.like(f'%{title}%'), ~KnowledgeData.is_deleted), orders=(KnowledgeData.id.desc()))
    items = []
    for rec in knowledge_datas:
        d = rec.dict('id', 'title', 'file_name', 'status', 'create_time', 'import_type')
        # 状态描述
        d['status_desc'] = KnowledgeDataStatus.get_element_value(rec.status)
        items.append(d)

    return json_response(data=items)


@knowledge_router.post('/knowledge/data/delete', summary='知识库数据删除')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True,)
async def do_knowledge_data_delete(request: Request, id: int = Body(..., title='知识库数据id', embed=True), session=None):
    """
    **desc**: 知识库数据列表

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/delete

    **method**: POST

    **params**:

        <query>:
            id: 知识库数据id

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    await KnowledgeService.delete_knowledge_data(session, knowledge_data_id=id)

    return json_response()


@knowledge_router.get('/knowledge/data/detail', summary='知识库数据详情')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True,)
async def get_knowledge_data_detail(request: Request,
                                    id: int = Query(..., title='知识库数据id'),
                                    knowledge_id: Optional[int] = Query(None, title='知识库id'),
                                    session=None):
    """
    **desc**: 知识库数据详情

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/detail

    **method**: GET

    **params**:

        <query>:
            id: 知识库数据id
            knowledge_id: 知识库id

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data: {
                title: 知识库数据名称
                file_name: 导入文件名称
                status: 知识库数据状态 状态 init(初始化)/pdf_converting(pdf文件转化中)/md_converting(md文件转化中)/embedding(向量化中)/success(已完成)
                status_desc: 状态描述
                create_time: 时间,
                text: 文件中提取的文本
                knowledge_id: 绑定的知识库id
                import_mode: 导入模式: image_text(图文)/qa(QA对)/text(文本)
                file_url: 上传的文件链接
                md_url: 最终md文件链接(url)
                knowledge_details: [
                    id: 知识库详情id
                    knowledge_id: 知识库id
                    knowledge_data_id: 知识库数据id
                    question: 知识库问题
                    answer: 知识库答案
                ] 数据库详情列表
                feishu_url: 飞书链接
                import_type: 导入类型 local_file(本地文件)/feishu_url(飞书链接)
                flush_frequency: 更新频率 minute(按分钟)/hour(按小时)/day(按天)/no_auto(不自动更新)
                flush_frequency_value: 更新频率值
                flush_start_time: 开始导入/刷新时间
                flush_end_time: 结束导入/刷新时间
                fail_message: 失败原因(会记录后台知识库导入异常)
            }
            exec_time: 接口后端处理时长
        }
    """
    knowledge_data = await KnowledgeData.fetch_one(session, wheres=(KnowledgeData.id == id))
    ret_data = knowledge_data.dict() if knowledge_data else {}

    # 数据库数据详情
    wheres = list()
    if knowledge_id is not None:
        wheres.append(KnowledgeDetail.knowledge_id == knowledge_id)
    wheres.append(KnowledgeDetail.knowledge_data_id == id)
    wheres.append(~KnowledgeDetail.is_deleted)

    knowledge_details = await KnowledgeDetail.fetch_all(session, wheres=wheres, orders=(KnowledgeDetail.id.desc()))
    ret_data['knowledge_details'] = [v.dict() for v in knowledge_details]

    return json_response(data=ret_data)


@knowledge_router.post('/knowledge/data/detail/add', summary='添加知识库数据详情')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True)
async def add_knowledge_data_detail(request: Request,
                                    knowledge_id: int = Body(..., title='知识库id'),
                                    knowledge_data_id: int = Body(..., title='知识库数据id'),
                                    text: str = Body(..., max_length=500,  title='知识库文本'),
                                    session=None,
                                    body_params=None):
    """
    **desc**: 添加知识库数据详情

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/detail/add

    **method**: POST

    **params**:

        <body>:
            knowledge_id: 知识库id
            knowledge_data_id: 知识库数据id
            text: 知识库文本

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    knowledge_detail = await KnowledgeService.add_knowledge_detail(session, body_params)

    return json_response(data=knowledge_detail.dict('id'))


@knowledge_router.post('/knowledge/data/detail/update', summary='修改知识库数据详情')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True)
async def update_knowledge_data_detail(request: Request,
                                       id: int = Body(..., title='知识库详情id'),
                                       text: str = Body(..., max_length=500,  title='知识库文本'),
                                       session=None,
                                       body_params=None):
    """
    **desc**: 修改知识库数据详情

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/detail/update

    **method**: POST

    **params**:

        <body>:
            id: 知识库数据详情id
            text: 知识库文本

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    await KnowledgeService.update_knowledge_detail(session, body_params)

    return json_response()


@knowledge_router.post('/knowledge/data/detail/delete', summary='删除知识库数据详情')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),), verify_login=True)
@exec_duration(add_return=True)
async def delete_knowledge_data_detail(request: Request, id: int = Body(..., title='知识库数据详情id', embed=True), session=None):
    """
    **desc**: 删除知识库数据详情

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/detail/delete

    **method**: POST

    **params**:

        <body>:
            id: 知识库数据详情id

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    await KnowledgeService.delete_knowledge_detail(session, knowledge_detail_id=id)

    return json_response()


@knowledge_router.post('/knowledge/data/feishu/flush', summary='飞书链接刷新')
@api_common(doc_replace=(('{0}', Config.ENV_DOC_FIX),))
@exec_duration(add_return=True,)
async def do_knowledge_data_feishu_flush(request: Request,
                                         id: int = Body(..., title='知识库数据id'),
                                         mode: str = Body('manual', title='执行模式', description='manual(手动)/auto(自动)'),
                                         session=None):
    """
    **desc**: 飞书链接刷新

    **url**: http(s)://{0}.udicaria.com/m/knowledge/data/feishu/flush

    **method**: POST

    **params**:

        <body>:
            id: 知识库数据id
            mode: 执行模式  manual(手动)/auto(自动)

    **return**:json数据结构::

        {
            code: 0(成功)/非0(失败)
            message: 成功返回success, 否则返回error信息
            data:
            exec_time: 接口后端处理时长
        }
    """
    await KnowledgeService.do_knowledge_data_feishu_flush(session, id)

    if mode == 'auto':
        await KnowledgeService.set_feishu_flush_crontab(session, id)

    return json_response()
